// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "media/formats/webm/webm_content_encodings_client.h"

#include "base/logging.h"
#include "media/formats/webm/webm_constants.h"

namespace media {

WebMContentEncodingsClient::WebMContentEncodingsClient(
    const scoped_refptr<MediaLog>& media_log)
    : media_log_(media_log)
    , content_encryption_encountered_(false)
    , content_encodings_ready_(false)
{
}

WebMContentEncodingsClient::~WebMContentEncodingsClient()
{
}

const ContentEncodings& WebMContentEncodingsClient::content_encodings() const
{
    DCHECK(content_encodings_ready_);
    return content_encodings_;
}

WebMParserClient* WebMContentEncodingsClient::OnListStart(int id)
{
    if (id == kWebMIdContentEncodings) {
        DCHECK(!cur_content_encoding_.get());
        DCHECK(!content_encryption_encountered_);
        content_encodings_.clear();
        content_encodings_ready_ = false;
        return this;
    }

    if (id == kWebMIdContentEncoding) {
        DCHECK(!cur_content_encoding_.get());
        DCHECK(!content_encryption_encountered_);
        cur_content_encoding_.reset(new ContentEncoding());
        return this;
    }

    if (id == kWebMIdContentEncryption) {
        DCHECK(cur_content_encoding_.get());
        if (content_encryption_encountered_) {
            MEDIA_LOG(ERROR, media_log_) << "Unexpected multiple ContentEncryption.";
            return NULL;
        }
        content_encryption_encountered_ = true;
        return this;
    }

    if (id == kWebMIdContentEncAESSettings) {
        DCHECK(cur_content_encoding_.get());
        return this;
    }

    // This should not happen if WebMListParser is working properly.
    DCHECK(false);
    return NULL;
}

// Mandatory occurrence restriction is checked in this function. Multiple
// occurrence restriction is checked in OnUInt and OnBinary.
bool WebMContentEncodingsClient::OnListEnd(int id)
{
    if (id == kWebMIdContentEncodings) {
        // ContentEncoding element is mandatory. Check this!
        if (content_encodings_.empty()) {
            MEDIA_LOG(ERROR, media_log_) << "Missing ContentEncoding.";
            return false;
        }
        content_encodings_ready_ = true;
        return true;
    }

    if (id == kWebMIdContentEncoding) {
        DCHECK(cur_content_encoding_.get());

        //
        // Specify default values to missing mandatory elements.
        //

        if (cur_content_encoding_->order() == ContentEncoding::kOrderInvalid) {
            // Default value of encoding order is 0, which should only be used on the
            // first ContentEncoding.
            if (!content_encodings_.empty()) {
                MEDIA_LOG(ERROR, media_log_) << "Missing ContentEncodingOrder.";
                return false;
            }
            cur_content_encoding_->set_order(0);
        }

        if (cur_content_encoding_->scope() == ContentEncoding::kScopeInvalid)
            cur_content_encoding_->set_scope(ContentEncoding::kScopeAllFrameContents);

        if (cur_content_encoding_->type() == ContentEncoding::kTypeInvalid)
            cur_content_encoding_->set_type(ContentEncoding::kTypeCompression);

        // Check for elements valid in spec but not supported for now.
        if (cur_content_encoding_->type() == ContentEncoding::kTypeCompression) {
            MEDIA_LOG(ERROR, media_log_) << "ContentCompression not supported.";
            return false;
        }

        // Enforce mandatory elements without default values.
        DCHECK(cur_content_encoding_->type() == ContentEncoding::kTypeEncryption);
        if (!content_encryption_encountered_) {
            MEDIA_LOG(ERROR, media_log_) << "ContentEncodingType is encryption but"
                                         << " ContentEncryption is missing.";
            return false;
        }

        content_encodings_.push_back(std::move(cur_content_encoding_));
        content_encryption_encountered_ = false;
        return true;
    }

    if (id == kWebMIdContentEncryption) {
        DCHECK(cur_content_encoding_.get());
        // Specify default value for elements that are not present.
        if (cur_content_encoding_->encryption_algo() == ContentEncoding::kEncAlgoInvalid) {
            cur_content_encoding_->set_encryption_algo(
                ContentEncoding::kEncAlgoNotEncrypted);
        }
        return true;
    }

    if (id == kWebMIdContentEncAESSettings) {
        if (cur_content_encoding_->cipher_mode() == ContentEncoding::kCipherModeInvalid)
            cur_content_encoding_->set_cipher_mode(ContentEncoding::kCipherModeCtr);
        return true;
    }

    // This should not happen if WebMListParser is working properly.
    DCHECK(false);
    return false;
}

// Multiple occurrence restriction and range are checked in this function.
// Mandatory occurrence restriction is checked in OnListEnd.
bool WebMContentEncodingsClient::OnUInt(int id, int64_t val)
{
    DCHECK(cur_content_encoding_.get());

    if (id == kWebMIdContentEncodingOrder) {
        if (cur_content_encoding_->order() != ContentEncoding::kOrderInvalid) {
            MEDIA_LOG(ERROR, media_log_)
                << "Unexpected multiple ContentEncodingOrder.";
            return false;
        }

        if (val != static_cast<int64_t>(content_encodings_.size())) {
            // According to the spec, encoding order starts with 0 and counts upwards.
            MEDIA_LOG(ERROR, media_log_) << "Unexpected ContentEncodingOrder.";
            return false;
        }

        cur_content_encoding_->set_order(val);
        return true;
    }

    if (id == kWebMIdContentEncodingScope) {
        if (cur_content_encoding_->scope() != ContentEncoding::kScopeInvalid) {
            MEDIA_LOG(ERROR, media_log_)
                << "Unexpected multiple ContentEncodingScope.";
            return false;
        }

        if (val == ContentEncoding::kScopeInvalid || val > ContentEncoding::kScopeMax) {
            MEDIA_LOG(ERROR, media_log_) << "Unexpected ContentEncodingScope.";
            return false;
        }

        if (val & ContentEncoding::kScopeNextContentEncodingData) {
            MEDIA_LOG(ERROR, media_log_) << "Encoded next ContentEncoding is not "
                                            "supported.";
            return false;
        }

        cur_content_encoding_->set_scope(static_cast<ContentEncoding::Scope>(val));
        return true;
    }

    if (id == kWebMIdContentEncodingType) {
        if (cur_content_encoding_->type() != ContentEncoding::kTypeInvalid) {
            MEDIA_LOG(ERROR, media_log_)
                << "Unexpected multiple ContentEncodingType.";
            return false;
        }

        if (val == ContentEncoding::kTypeCompression) {
            MEDIA_LOG(ERROR, media_log_) << "ContentCompression not supported.";
            return false;
        }

        if (val != ContentEncoding::kTypeEncryption) {
            MEDIA_LOG(ERROR, media_log_) << "Unexpected ContentEncodingType " << val
                                         << ".";
            return false;
        }

        cur_content_encoding_->set_type(static_cast<ContentEncoding::Type>(val));
        return true;
    }

    if (id == kWebMIdContentEncAlgo) {
        if (cur_content_encoding_->encryption_algo() != ContentEncoding::kEncAlgoInvalid) {
            MEDIA_LOG(ERROR, media_log_) << "Unexpected multiple ContentEncAlgo.";
            return false;
        }

        if (val < ContentEncoding::kEncAlgoNotEncrypted || val > ContentEncoding::kEncAlgoAes) {
            MEDIA_LOG(ERROR, media_log_) << "Unexpected ContentEncAlgo " << val
                                         << ".";
            return false;
        }

        cur_content_encoding_->set_encryption_algo(
            static_cast<ContentEncoding::EncryptionAlgo>(val));
        return true;
    }

    if (id == kWebMIdAESSettingsCipherMode) {
        if (cur_content_encoding_->cipher_mode() != ContentEncoding::kCipherModeInvalid) {
            MEDIA_LOG(ERROR, media_log_)
                << "Unexpected multiple AESSettingsCipherMode.";
            return false;
        }

        if (val != ContentEncoding::kCipherModeCtr) {
            MEDIA_LOG(ERROR, media_log_) << "Unexpected AESSettingsCipherMode " << val
                                         << ".";
            return false;
        }

        cur_content_encoding_->set_cipher_mode(
            static_cast<ContentEncoding::CipherMode>(val));
        return true;
    }

    // This should not happen if WebMListParser is working properly.
    DCHECK(false);
    return false;
}

// Multiple occurrence restriction is checked in this function.  Mandatory
// restriction is checked in OnListEnd.
bool WebMContentEncodingsClient::OnBinary(int id,
    const uint8_t* data,
    int size)
{
    DCHECK(cur_content_encoding_.get());
    DCHECK(data);
    DCHECK_GT(size, 0);

    if (id == kWebMIdContentEncKeyID) {
        if (!cur_content_encoding_->encryption_key_id().empty()) {
            MEDIA_LOG(ERROR, media_log_) << "Unexpected multiple ContentEncKeyID";
            return false;
        }
        cur_content_encoding_->SetEncryptionKeyId(data, size);
        return true;
    }

    // This should not happen if WebMListParser is working properly.
    DCHECK(false);
    return false;
}

} // namespace media
